package utils

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"go-chain-api/global"
	models "go-chain-api/internal/model"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"io/ioutil"
	"log"
	"math"
	"math/big"
	"net/http"
	"net/url"
	"strings"
)

// MD5 Md5加密
func MD5(str string) string {
	// 创建一个MD5哈希对象
	hash := md5.New()
	// 写入要加密的数据
	hash.Write([]byte(str))
	// 计算MD5哈希值
	sum := hash.Sum(nil)
	// 将哈希值转换为十六进制字符串
	md5String := hex.EncodeToString(sum)
	return md5String
}

// GetUserInfoByApiKey 根据ApiKey获取用户
func GetUserInfoByApiKey(c *gin.Context, apiKey string) models.User {
	var user models.User
	user, err := user.GetUserByApiKey(apiKey)
	if err != nil {
		c.JSON(http.StatusOK, global.Response{
			Code: http.StatusInternalServerError,
			Data: nil,
			Msg:  "未查询到用户信息",
		})
	}
	return user
}

func SendCallBack(body *models.CallBackBody, key string, nonce string, timestamp string, urls string) error {
	//解析body
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return err
	}
	data := []byte(string(bodyJson) + key + nonce + timestamp)
	hash := md5.Sum(data)
	sign := hex.EncodeToString(hash[:])
	sendBody := url.Values{
		"timestamp": {timestamp},
		"nonce":     {nonce},
		"sign":      {sign},
		"body":      {string(bodyJson)},
	}
	err = SendPost(sendBody, urls)
	if err != nil {
		return err
	}
	return nil
}

func SendPost(data url.Values, urls string) error {

	resp, err := http.PostForm(urls, data)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	return nil
}

func SendCallBackV2(body *models.CallBackBody, key string, nonce string, timestamp string, urls string) (string, int, error) {
	//解析body
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return "", 0, err
	}
	data := []byte(string(bodyJson) + key + nonce + timestamp)
	hash := md5.Sum(data)
	sign := hex.EncodeToString(hash[:])
	sendBody := url.Values{
		"timestamp": {timestamp},
		"nonce":     {nonce},
		"sign":      {sign},
		"body":      {string(bodyJson)},
	}
	responseData, statusCode, err := SendPostV2(sendBody, urls)
	if err != nil {
		return "", 0, err
	}
	return responseData, statusCode, nil
}

func SendPostV2(data url.Values, urls string) (string, int, error) {

	resp, err := http.PostForm(urls, data)
	if err != nil {
		return "", 0, err
	}
	defer resp.Body.Close()

	responseData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", 0, err
	}
	return string(responseData), resp.StatusCode, nil
}

func GetSign(body *models.CallBackBody, key string, nonce string, timestamp string) (string, error) {
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return "", err
	}
	data := []byte(string(bodyJson) + key + nonce + timestamp)
	hash := md5.Sum(data)
	sign := hex.EncodeToString(hash[:])
	return sign, nil
}

func SendCallBackV3(body *models.CallBackBody, sign string, nonce string, timestamp string, urls string, insertedId primitive.ObjectID) error {
	//解析body
	bodyJson, err := json.Marshal(body)
	if err != nil {
		return err
	}
	sendBody := url.Values{
		"timestamp": {timestamp},
		"nonce":     {nonce},
		"sign":      {sign},
		"body":      {string(bodyJson)},
	}
	err, respBody, statusCode := SendPostV3(sendBody, urls)
	if err != nil {
		return err
	}
	update := bson.D{{"callBackResult", respBody}}
	if statusCode == 200 {
		update = append(update, bson.E{Key: "verify", Value: 1})
	}
	_, err = global.DBEngine.Database(global.DbConfig.DbName).Collection("collection_callback").UpdateOne(context.Background(), bson.M{"_id": insertedId}, bson.M{"$set": update})
	if err != nil {
		log.Println("更新collection_callback失败:", err)
		return err
	}
	return nil
}

func SendPostV3(data url.Values, urls string) (error, string, int) {

	resp, err := http.PostForm(urls, data)
	if err != nil {
		return err, "", 0
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err, "", 0
	}
	return nil, string(respBody), resp.StatusCode
}

func GetMainBalance(client *ethclient.Client, address string, c *gin.Context) (string, error) {
	// 查询账户余额
	balance, err := client.BalanceAt(c, common.HexToAddress(address), nil)
	if err != nil {
		return "", err
	}

	// 将余额从wei转换为以太币
	ethBalance := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
	ethBalanceStr := ethBalance.Text('f', 9)
	return ethBalanceStr, nil
}

func GetTokenBalance(client *ethclient.Client, c *gin.Context, tokenAddress string, accountAddress string, decimals int32) (string, error) {
	// 代币合约ABI
	parsedABI, err := abi.JSON(strings.NewReader(ERC20_ABI))
	if err != nil {

		return "", err
	}
	contractAddress := common.HexToAddress(tokenAddress)
	data, err := parsedABI.Pack("balanceOf", common.HexToAddress(accountAddress))
	if err != nil {
		return "", err
	}
	// 调用balanceOf方法
	callResult, err := client.CallContract(c, ethereum.CallMsg{
		To:   &contractAddress,
		Data: data,
		From: common.HexToAddress(accountAddress),
	}, nil)
	if err != nil {
		return "", err
	}

	// 解析结果
	balance, err := parsedABI.Unpack("balanceOf", callResult)
	if err != nil {

		return "", err
	}
	tokenBalance := new(big.Float).Quo(new(big.Float).SetInt(balance[0].(*big.Int)), big.NewFloat(math.Pow10(int(decimals))))

	balanceStr := tokenBalance.Text('f', 4) // 将结果格式化为固定点数表示，小数点后保留的位数与代币的小数位数相同
	return balanceStr, nil
}

func CallContractMethod(client *ethclient.Client, contractAddress common.Address, contractABI string, methodName string, params ...interface{}) ([]interface{}, error) {
	// 解析合约ABI
	parsedABI, err := abi.JSON(strings.NewReader(contractABI))
	if err != nil {
		return nil, fmt.Errorf("failed to parse contract ABI: %v", err)
	}

	// 打包合约调用数据
	packedData, err := parsedABI.Pack(methodName, params...)
	if err != nil {
		return nil, fmt.Errorf("failed to pack data for method %s: %v", methodName, err)
	}

	callMsg := ethereum.CallMsg{
		To:   &contractAddress,
		Data: packedData,
	}

	// 调用合约
	ctx := context.Background()
	result, err := client.CallContract(ctx, callMsg, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to call contract method %s: %v", methodName, err)
	}

	// 解包合约调用结果
	outputs, err := parsedABI.Unpack(methodName, result)
	if err != nil {
		return nil, fmt.Errorf("failed to unpack return data for method %s: %v", methodName, err)
	}

	return outputs, nil
}
